package com.swipemaster.moves;

import com.swipemaster.common.Coords;
import com.swipemaster.elements.Element;

/** Path is formed by moving from one target to another. By default modulus order is on. For 3 targets visiting order
 * will be: 0, 1, 2, 0, 1, 2... If we turn modulus order off, then it will be: 0, 1, 2, 1, 0, 1... */
public class PathMove extends Move
{
	/** Maximal number of targets. */
	public static final int MAX_TARGETS = 10;
	/** Constant speed in pixels per ms. */
	public double speed;
	/** Actual target number. */
	public final int[] targetsCoords = new int[2 * MAX_TARGETS];
	/** Number of targets. Half of targets coordinates table length. */
	public int actualTarget = 0;
	/** Even indexes has x screen coordinates, odd - y coordinates. [x1, y1, x2, y2, ...] */
	public int numberOfTargets;
	/** Used by snake to see number of targets ahead. */
	public int targetsAhead = 0;
	/** Used by snake to see if the head element reached his target. */
	public boolean reached = false;
	/** If true, element from the last target instantly teleports to the first. */
	public boolean moduloMove = false;
	/** If true, targets visiting order: 0, 1, 2, 0, 1, 2... If false: 0, 1, 2, 1, 0, 1... */
	public boolean moduloOrder = true;
	/** If modulo is false, in which direction in the list of point we are going. */
	public boolean up = true;
	/** Pixels that would be lost while changing target. */
	public int restPixels = 0;

	/** @param targets Universal coordinates of targets given in order [x1, y1, x2, y2, ...].
	 * @param speed Speed of move. Universal units. */
	public PathMove(double[] targets, double speed)
	{
		if (targets.length < 2)
		{
			throw new IllegalArgumentException(
					"Too short table of target coords. If you want steady enemy, use NoMove.");
		}
		if (targets.length > (2 * MAX_TARGETS))
		{
			throw new IllegalArgumentException("Too many targets");
		}
		this.speed = Coords.mulByDiag(speed);
		numberOfTargets = targets.length / 2;
		targetsAhead = numberOfTargets;
		for (int i = 0; i < targets.length; i += 2)
		{
			targetsCoords[i] = Coords.x(targets[i]);
			targetsCoords[i + 1] = Coords.y(targets[i + 1]);
		}
	}

	public PathMove(PathMove otherMove)
	{
		setTargets(otherMove.targetsCoords);
		speed = otherMove.speed;
		actualTarget = otherMove.actualTarget;
		numberOfTargets = otherMove.numberOfTargets;
		targetsAhead = otherMove.targetsAhead;
		reached = otherMove.reached;
		moduloMove = otherMove.moduloMove;
		moduloOrder = otherMove.moduloOrder;
		up = otherMove.up;
		restPixels = otherMove.restPixels;
	}

	public void appendTarget(int[] target)
	{
		final int pos = (actualTarget + targetsAhead) % MAX_TARGETS;
		targetsCoords[2 * pos] = target[0];
		targetsCoords[(2 * pos) + 1] = target[1];
		targetsAhead++;
		if (numberOfTargets < MAX_TARGETS)
		{
			numberOfTargets++;
		}
	}

	@Override
	public void calculateNextPosition(Element element, int interval)
	{
		int x = element.x;
		int y = element.y;
		// pixels to move (sum in both axis) in this step
		int pixels = (int) Math.round(speed * interval);
		pixels += restPixels;
		restPixels = 0;

		// distance to next target point
		int dist = Coords.distance(x, y, targetsCoords[2 * actualTarget], targetsCoords[(2 * actualTarget) + 1]);

		// if target point will be reached in this step
		if (pixels >= dist)
		{
			if (targetsAhead > 0)
			{
				targetsAhead--;
			}
			reached = true;

			pixels -= dist;
			x = targetsCoords[2 * actualTarget];
			y = targetsCoords[(2 * actualTarget) + 1];

			// choosing next target point
			if (moduloOrder)
			{
				actualTarget++;
				actualTarget %= numberOfTargets;
				if (moduloMove)
				{
					if (actualTarget == 0)
					{
						x = targetsCoords[2 * actualTarget];
						y = targetsCoords[(2 * actualTarget) + 1];
					}
				}
			}
			else
			{
				if (up)
				{
					actualTarget++;
					if (actualTarget >= numberOfTargets)
					{
						up = false;
					}
				}
				else
				{
					actualTarget--;
					if (actualTarget <= 0)
					{
						up = true;
					}
				}
				actualTarget %= numberOfTargets;
				if (actualTarget < 0)
				{
					actualTarget = 0;
				}
			}
			if (numberOfTargets == 1)
			{
				restPixels = pixels;
				dist = 1;
			}
			else
			{
				dist = Coords.distance(x, y, targetsCoords[2 * actualTarget], targetsCoords[(2 * actualTarget) + 1]);
			}
		}

		final int xDist = targetsCoords[2 * actualTarget] - x;
		final int yDist = targetsCoords[(2 * actualTarget) + 1] - y;

		final double constant = (double) pixels / dist;

		nextX = x + (int) Math.round(constant * xDist);
		nextY = y + (int) Math.round(constant * yDist);
	}

	/** @param moduloOrder If true, targets visiting order: 0, 1, 2, 0, 1, 2... If false: 0, 1, 2, 1, 0, 1... */
	public void setModulo(boolean moduloOrder)
	{
		this.moduloOrder = moduloOrder;
	}

	/** @param moduloMove If true, element from the last target instantly teleports to the first. */
	public void setModuloMove(boolean moduloMove)
	{
		this.moduloMove = moduloMove;
		if (moduloMove)
		{
			moduloOrder = true;
		}
	}

	public void setTargets(double[] targets)
	{
		if (targets.length < 2)
		{
			throw new IllegalArgumentException(
					"Too short table of target coords. If you want steady enemy, use NoMove.");
		}
		if (targets.length > (2 * MAX_TARGETS))
		{
			throw new IllegalArgumentException("Too many targets");
		}
		numberOfTargets = targets.length / 2;
		targetsAhead = numberOfTargets;
		reached = false;
		for (int i = 0; i < targets.length; i += 2)
		{
			targetsCoords[i] = Coords.x(targets[i]);
			targetsCoords[i + 1] = Coords.y(targets[i + 1]);
		}
	}

	public void setTargets(int[] targets)
	{
		if (targets.length < 2)
		{
			throw new IllegalArgumentException(
					"Too short table of target coords. If you want steady enemy, use NoMove.");
		}
		if (targets.length > (2 * MAX_TARGETS))
		{
			throw new IllegalArgumentException("Too many targets");
		}
		numberOfTargets = targets.length / 2;
		targetsAhead = numberOfTargets;
		reached = false;
		for (int i = 0; i < targets.length; i += 2)
		{
			targetsCoords[i] = targets[i];
			targetsCoords[i + 1] = targets[i + 1];
		}
	}

	@Override
	public void speedUp(double scale)
	{
		speed *= scale;
	}
}
